/**
 * 配置参数，URL方式配置WebSocket连接
 */
export interface MyWebSocketConfig {
  /**
   * @example 'ws://127.0.0.1:8080/topic/test'
   */
  wsUrl: string
}
/**
 * WebSocket连接实例，封装了WebSocket的创建、关闭和事件监听等操作。
 */
export class MyWebSocket {
  protected _wsUrl = ''
  protected _webSocket: WebSocket | null = null
  // 是否自动重连
  protected _autoReconnect = true
  // 最大重连次数
  protected _maxRetries = 50
  // 重连尝试次数
  protected _retryCount = 0
  // 发送心跳数据间隔
  protected _heartbeatInterval = 1000 * 30
  // 计时器id
  protected _heartbeatTimer?: any
  // 处理批次大小
  protected _batchSize = 10
  // 消息处理队列，防止后端发送消息太快导致的页面假死
  protected _processCache: any[] = []
  // 是否正在处理
  protected _isProcessing = false
  // 是否暂停处理
  protected _isProcessPause = false
  // 是否停止处理
  protected _isProcessStop = false

  static #instanceMap: Map<string, MyWebSocket> = new Map()

  /**
   * 获取指定 WebSocket 地址对应的 WebSocket 实例。
   * @param wsUrl WebSocket 地址
   * @returns 返回指定 WebSocket 地址对应的 WebSocket 实例，如果不存在则创建一个新的 WebSocket 实例
   */
  static getInstance(wsUrl: string) {
    return MyWebSocket.#instanceMap.get(wsUrl) || new MyWebSocket(wsUrl)
  }

  /**
   * 构造函数
   * @param wsUrl WebSocket服务地址
   * @example 'ws://127.0.0.1:8080/topic/test'
   */
  constructor(wsUrl: string, callback?: (...args: any[]) => any) {
    this._wsUrl = wsUrl
    this.prepareSocket(callback)
    MyWebSocket.#instanceMap.set(wsUrl, this)
  }

  /**
   * 准备WebSocket连接， 新建实例时自动调用
   * @param callback 可选的回调函数，用于处理WebSocket接收到的数据
   */
  prepareSocket(callback?: (...args: any[]) => any) {
    if ('WebSocket' in window) {
      // 如果已有连接且处于可用状态，则不再创建新连接
      if (
        this._webSocket
        && (this._webSocket.readyState === WebSocket.OPEN
          || this._webSocket.readyState === WebSocket.CONNECTING)
      ) {
        return
      }
      const topic = this._wsUrl
      this._webSocket = new WebSocket(topic)
      this._webSocket.binaryType = 'arraybuffer'
      this._webSocket.onopen = () => {
        console.log(`topic：${topic}，【onopen】，连接成功！`)
        // 重置重试计数器
        this._retryCount = 0
        // 重置处理状态
        this._isProcessPause = false
        this._isProcessing = false
        this._isProcessStop = false
        this._processCache = []
        // 启动心跳机制
        this.startHeartbeat()
      }
      this._webSocket.onmessage = (evt: any) => {
        if (this._isProcessStop)
          return
        this._processCache.push({ topic, evt, callback })
        if (!this._isProcessing) {
          this._processQueue()
          this._isProcessing = true
        }
      }

      this._webSocket.onclose = () => {
        console.log(`topic：${topic}，【onclose】，连接关闭！`)
        this.stopHeartbeat()
        if (this._autoReconnect) {
          // 超过最大重试次数则停止
          if (this._retryCount > this._maxRetries) {
            console.error(`topic：${topic}，达到最大重试次数，停止重连`)
            return
          }
          const delay = Math.min(1000 * 2 ** this._retryCount, 1000 * 10) // 指数退避，最大10秒
          this._retryCount++
          console.log(`${delay}ms后尝试第${this._retryCount}次重连...`)
          setTimeout(() => {
            this.prepareSocket(callback)
          }, delay)
        }
      }

      this._webSocket.onerror = () => {
        console.log(`topic：${topic}，【onerror】，连接异常！`)
      }
    }
  }

  // 消息批处理函数
  _processQueue() {
    if (this._processCache.length == 0 || this._isProcessStop) {
      this._isProcessing = false
      return
    }
    if (this._isProcessPause) {
      setTimeout(() => {
        this._processQueue()
      }, 1000)
      return
    }
    const batch = this._processCache.splice(0, this._batchSize)
    // 使用setTimeout让浏览器有机会渲染
    setTimeout(async () => {
      for (let index = 0; index < batch.length; index++) {
        const { topic, evt, callback } = batch[index]
        if (!this._isProcessStop) {
          await this._processSingleMessage(topic, evt, callback)
        }
      }
      // 处理下一批
      this._processQueue()
    }, 0)
  }

  // 处理单条消息
  async _processSingleMessage(topic: any, evt: any, callback: any) {
    if (this._isProcessStop)
      return
    console.log(`topic：${topic}，【onmessage】，接收ws数据：${evt.data}`)
    let data: any = evt.data
    try {
      if (evt.data instanceof ArrayBuffer) {
        data = JSON.parse(
          new TextDecoder('utf-8').decode(new Uint8Array(evt.data)),
        )
      }
      else if (typeof evt.data == 'string') {
        data = JSON.parse(evt.data)
      }
    }
    catch (error) {
      console.log('WebSocket返回值JSON序列化失败')
      data = { data }
    }
    await callback?.(data)
  }

  processPlay() {
    this._isProcessPause = false
    this._isProcessStop = false
  }

  processPause() {
    this._isProcessPause = true
  }

  processStop() {
    this._isProcessStop = true
    this._isProcessing = false
    this._processCache = []
  }

  /**
   * 关闭WebSocket连接并重置连接实例
   */
  destroySocket() {
    this._autoReconnect = false
    this._webSocket?.close()
    MyWebSocket.#instanceMap.delete(this._wsUrl)
    this._webSocket = null
  }

  /**
   * 为WebSocket连接添加事件监听器。
   *
   * @param type 事件类型，可以是 'close'、'error'、'message' 或 'open'。
   * @param callback 事件触发时调用的回调函数，this指向WebSocket对象，回调函数参数为事件对象。
   * @param once 是否为一次性监听器，默认为false。
   * @returns 返回移除监听器的函数，调用该函数可以移除对应的事件监听器。
   */
  addEventListener(
    type: 'close' | 'error' | 'message' | 'open',
    callback: (evt: Event | CloseEvent | MessageEvent<any>) => any,
    once?: boolean,
  ) {
    let removeListener: null | (() => void) = null
    if (
      this._webSocket?.readyState === WebSocket.OPEN
      || this._webSocket?.readyState === WebSocket.CONNECTING
    ) {
      const _callback = (_evt: Event | MessageEvent<any> | CloseEvent) => {
        if (this._isProcessStop)
          return
        this._processCache.push({ topic: this._wsUrl, evt: _evt, callback })
        if (!this._isProcessing) {
          this._processQueue()
          this._isProcessing = true
        }
      }
      this._webSocket.addEventListener(type, _callback, {
        once,
      })
      removeListener = () => {
        this.removeEventListener(type, _callback)
      }
    }
    return removeListener
  }

  /**
   * 从WebSocket对象中移除事件监听器。
   *
   * @param type 事件类型，可以是'close'、'error'、'message'或'open'。
   * @param callback 当事件触发时调用的回调函数，回调函数的上下文（this）将被设置为WebSocket对象，参数ev是事件对象。
   */
  removeEventListener(
    type: 'close' | 'error' | 'message' | 'open',
    callback: (
      this: WebSocket,
      ev: Event | CloseEvent | MessageEvent<any>,
    ) => any,
  ) {
    if (
      this._webSocket?.readyState === WebSocket.OPEN
      || this._webSocket?.readyState === WebSocket.CONNECTING
    ) {
      this._webSocket.removeEventListener(type, callback)
    }
  }

  /**
   * 发送消息
   *
   * @param message 要发送的消息
   */
  async doSend(message: string) {
    const maxWaitTime = 3000
    return new Promise<void>((resolve, reject) => {
      let timer = 0
      const interval = setInterval(() => {
        if (this._webSocket?.readyState === WebSocket.OPEN) {
          this._webSocket.send(message)
          clearInterval(interval)
          resolve()
        }
        else {
          timer += 100
          if (timer > maxWaitTime) {
            clearInterval(interval)
            console.error('WebSocket连接超时，请检查网络或服务器状态')
            reject(new Error('WebSocket连接超时，请检查网络或服务器状态'))
          }
        }
      }, 100)
    })
  }

  // 启动发送心跳
  startHeartbeat() {
    this.stopHeartbeat()
    this._heartbeatTimer = setInterval(() => {
      this.doSend(JSON.stringify({ type: 'heartBeat', data: {} }))
        .then(() => {
          console.log('WebSocket', '已发送心跳数据...')
        })
        .catch((e) => {
          console.error(e)
        })
    }, this._heartbeatInterval)
  }

  // 停止发送心跳
  stopHeartbeat() {
    clearInterval(this._heartbeatTimer)
    this._heartbeatTimer = undefined
  }
}
